﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace SMARTQuery
{
    /// <summary>
    /// 硬盘SMART信息查询工具类
    /// </summary>
    public class SMARTQueryUtil
    {
        #region DllImport

        #region EFileAccess
        public enum EFileAccess : uint
        {
            //
            // Standard Section
            //

            AccessSystemSecurity = 0x1000000,   // AccessSystemAcl access type
            MaximumAllowed = 0x2000000,     // MaximumAllowed access type

            Delete = 0x10000,
            ReadControl = 0x20000,
            WriteDAC = 0x40000,
            WriteOwner = 0x80000,
            Synchronize = 0x100000,

            StandardRightsRequired = 0xF0000,
            StandardRightsRead = ReadControl,
            StandardRightsWrite = ReadControl,
            StandardRightsExecute = ReadControl,
            StandardRightsAll = 0x1F0000,
            SpecificRightsAll = 0xFFFF,

            FILE_READ_DATA = 0x0001,        // file & pipe
            FILE_LIST_DIRECTORY = 0x0001,       // directory
            FILE_WRITE_DATA = 0x0002,       // file & pipe
            FILE_ADD_FILE = 0x0002,         // directory
            FILE_APPEND_DATA = 0x0004,      // file
            FILE_ADD_SUBDIRECTORY = 0x0004,     // directory
            FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe
            FILE_READ_EA = 0x0008,          // file & directory
            FILE_WRITE_EA = 0x0010,         // file & directory
            FILE_EXECUTE = 0x0020,          // file
            FILE_TRAVERSE = 0x0020,         // directory
            FILE_DELETE_CHILD = 0x0040,     // directory
            FILE_READ_ATTRIBUTES = 0x0080,      // all
            FILE_WRITE_ATTRIBUTES = 0x0100,     // all

            //
            // Generic Section
            //

            GenericRead = 0x80000000,
            GenericWrite = 0x40000000,
            GenericExecute = 0x20000000,
            GenericAll = 0x10000000,

            SPECIFIC_RIGHTS_ALL = 0x00FFFF,
            FILE_ALL_ACCESS =
                StandardRightsRequired |
                Synchronize |
                0x1FF,

            FILE_GENERIC_READ =
                StandardRightsRead |
                FILE_READ_DATA |
                FILE_READ_ATTRIBUTES |
                FILE_READ_EA |
                Synchronize,

            FILE_GENERIC_WRITE =
                StandardRightsWrite |
                FILE_WRITE_DATA |
                FILE_WRITE_ATTRIBUTES |
                FILE_WRITE_EA |
                FILE_APPEND_DATA |
                Synchronize,

            FILE_GENERIC_EXECUTE =
                StandardRightsExecute |
                FILE_READ_ATTRIBUTES |
                FILE_EXECUTE |
                Synchronize
        }
        #endregion

        #region EFileShare
        [Flags]
        public enum EFileShare : uint
        {
            /// <summary>
            ///
            /// </summary>
            None = 0x00000000,
            /// <summary>
            /// Enables subsequent open operations on an object to request read access.
            /// Otherwise, other processes cannot open the object if they request read access.
            /// If this flag is not specified, but the object has been opened for read access, the function fails.
            /// </summary>
            Read = 0x00000001,
            /// <summary>
            /// Enables subsequent open operations on an object to request write access.
            /// Otherwise, other processes cannot open the object if they request write access.
            /// If this flag is not specified, but the object has been opened for write access, the function fails.
            /// </summary>
            Write = 0x00000002,
            /// <summary>
            /// Enables subsequent open operations on an object to request delete access.
            /// Otherwise, other processes cannot open the object if they request delete access.
            /// If this flag is not specified, but the object has been opened for delete access, the function fails.
            /// </summary>
            Delete = 0x00000004
        }
        #endregion

        #region ECreationDisposition
        public enum ECreationDisposition : uint
        {
            /// <summary>
            /// Creates a new file. The function fails if a specified file exists.
            /// </summary>
            New = 1,
            /// <summary>
            /// Creates a new file, always.
            /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes,
            /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
            /// </summary>
            CreateAlways = 2,
            /// <summary>
            /// Opens a file. The function fails if the file does not exist.
            /// </summary>
            OpenExisting = 3,
            /// <summary>
            /// Opens a file, always.
            /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
            /// </summary>
            OpenAlways = 4,
            /// <summary>
            /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
            /// The calling process must open the file with the GENERIC_WRITE access right.
            /// </summary>
            TruncateExisting = 5
        }
        #endregion

        #region EFileAttributes
        [Flags]
        public enum EFileAttributes : uint
        {
            Readonly = 0x00000001,
            Hidden = 0x00000002,
            System = 0x00000004,
            Directory = 0x00000010,
            Archive = 0x00000020,
            Device = 0x00000040,
            Normal = 0x00000080,
            Temporary = 0x00000100,
            SparseFile = 0x00000200,
            ReparsePoint = 0x00000400,
            Compressed = 0x00000800,
            Offline = 0x00001000,
            NotContentIndexed = 0x00002000,
            Encrypted = 0x00004000,
            Write_Through = 0x80000000,
            Overlapped = 0x40000000,
            NoBuffering = 0x20000000,
            RandomAccess = 0x10000000,
            SequentialScan = 0x08000000,
            DeleteOnClose = 0x04000000,
            BackupSemantics = 0x02000000,
            PosixSemantics = 0x01000000,
            OpenReparsePoint = 0x00200000,
            OpenNoRecall = 0x00100000,
            FirstPipeInstance = 0x00080000
        }
        #endregion

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern IntPtr CreateFile(
           string lpFileName,
           EFileAccess dwDesiredAccess,
           EFileShare dwShareMode,
           IntPtr lpSecurityAttributes,
           ECreationDisposition dwCreationDisposition,
           EFileAttributes dwFlagsAndAttributes,
           IntPtr hTemplateFile);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        #endregion

        #region 私有方法

        private void organizeSmartValue_NVMe(SMARTLog_NVMe smartLog, ref List<string[]> rowCollection)
        {
            rowCollection.Add(new string[4] { "0", "Critical Warning", smartLog.critical_warning.content, smartLog.critical_warning.raw });
            rowCollection.Add(new string[4] { "1-2", "Composite Temperature", smartLog.temperature.content, smartLog.temperature.raw });
            rowCollection.Add(new string[4] { "3", "Available Spare", smartLog.avail_spare.content, smartLog.avail_spare.raw });
            rowCollection.Add(new string[4] { "4", "Available Spare Threshold", smartLog.spare_thresh.content, smartLog.spare_thresh.raw });
            rowCollection.Add(new string[4] { "5", "Percentage Use", smartLog.percent_used.content, smartLog.percent_used.raw });
            rowCollection.Add(new string[4] { "6", "Endurance Group Critical Warning Summary", smartLog.endu_grp_crit_warn_sumry.content, smartLog.endu_grp_crit_warn_sumry.raw });
            rowCollection.Add(new string[4] { "7-31", "Reserved", smartLog.rsvd7.content, smartLog.rsvd7.raw });
            rowCollection.Add(new string[4] { "32-47", "Data Units Read", smartLog.data_units_read.content, smartLog.data_units_read.raw });
            rowCollection.Add(new string[4] { "48-63", "Data Units Written", smartLog.data_units_written.content, smartLog.data_units_written.raw });
            rowCollection.Add(new string[4] { "64-79", "Host Read Commands", smartLog.host_reads.content, smartLog.host_reads.raw });
            rowCollection.Add(new string[4] { "80-95", "Host Write Commands", smartLog.host_writes.content, smartLog.host_writes.raw });
            rowCollection.Add(new string[4] { "96-111", "Controller Busy Time", smartLog.ctrl_busy_time.content, smartLog.ctrl_busy_time.raw });
            rowCollection.Add(new string[4] { "112-127", "Power Cycles", smartLog.power_cycles.content, smartLog.power_cycles.raw });
            rowCollection.Add(new string[4] { "128-143", "Power On Hours", smartLog.power_on_hours.content, smartLog.power_on_hours.raw });
            rowCollection.Add(new string[4] { "144-159", "Unsafe Shutdowns", smartLog.unsafe_shutdowns.content, smartLog.unsafe_shutdowns.raw });
            rowCollection.Add(new string[4] { "160-175", "Media and Data integrity Errors", smartLog.media_errors.content, smartLog.media_errors.raw });
            rowCollection.Add(new string[4] { "176-191", "Number of Error information Log Entries", smartLog.num_err_log_entries.content, smartLog.num_err_log_entries.raw });
            rowCollection.Add(new string[4] { "192-195", "Warning Composite Temperature Time", smartLog.warning_temp_time.content, smartLog.warning_temp_time.raw });
            rowCollection.Add(new string[4] { "196-199", "Critical Composite Temperature Time", smartLog.critical_comp_time.content, smartLog.critical_comp_time.raw });
        }

        private void separatSmartValue_NVMe(byte[] buf_smart, ref SMARTLog_NVMe smartLog)
        {
            smartLog.critical_warning.content = (buf_smart[0]).ToString();
            smartLog.temperature.content = ((buf_smart[2] << 8) + buf_smart[1] - 273).ToString();
            smartLog.avail_spare.content = (buf_smart[3]).ToString();
            smartLog.spare_thresh.content = (buf_smart[4]).ToString();
            smartLog.percent_used.content = (buf_smart[5]).ToString();
            smartLog.endu_grp_crit_warn_sumry.content = Convert.ToString(buf_smart[6], 2).PadLeft(8, '0');
            smartLog.rsvd7.content = getTableData_NVMe(buf_smart, 7, 31, false);
            smartLog.data_units_read.content = getTableData_NVMe(buf_smart, 32, 47, false);
            smartLog.data_units_written.content = getTableData_NVMe(buf_smart, 48, 63, false);
            smartLog.host_reads.content = getTableData_NVMe(buf_smart, 64, 79, false);
            smartLog.host_writes.content = getTableData_NVMe(buf_smart, 80, 95, false);
            smartLog.ctrl_busy_time.content = getTableData_NVMe(buf_smart, 96, 111, false);
            smartLog.power_cycles.content = getTableData_NVMe(buf_smart, 112, 127, false);
            smartLog.power_on_hours.content = getTableData_NVMe(buf_smart, 128, 143, false);
            smartLog.unsafe_shutdowns.content = getTableData_NVMe(buf_smart, 144, 159, false);
            smartLog.media_errors.content = getTableData_NVMe(buf_smart, 160, 175, false);
            smartLog.num_err_log_entries.content = getTableData_NVMe(buf_smart, 176, 191, false);
            smartLog.warning_temp_time.content = getTableData_NVMe(buf_smart, 192, 195, false);
            smartLog.critical_comp_time.content = getTableData_NVMe(buf_smart, 196, 199, false);



            smartLog.critical_warning.raw = (buf_smart[0]).ToString("X8");
            smartLog.temperature.raw = ((buf_smart[2] << 8) + buf_smart[1]).ToString("X8");
            smartLog.avail_spare.raw = (buf_smart[3]).ToString("X8");
            smartLog.spare_thresh.raw = (buf_smart[4]).ToString("X8");
            smartLog.percent_used.raw = (buf_smart[5]).ToString("X8");
            smartLog.endu_grp_crit_warn_sumry.raw = (buf_smart[6]).ToString("X8");
            smartLog.rsvd7.raw = getTableData_NVMe(buf_smart, 7, 31, true);
            smartLog.data_units_read.raw = getTableData_NVMe(buf_smart, 32, 47, true);
            smartLog.data_units_written.raw = getTableData_NVMe(buf_smart, 48, 63, true);
            smartLog.host_reads.raw = getTableData_NVMe(buf_smart, 64, 79, true);
            smartLog.host_writes.raw = getTableData_NVMe(buf_smart, 80, 95, true);
            smartLog.ctrl_busy_time.raw = getTableData_NVMe(buf_smart, 96, 111, true);
            smartLog.power_cycles.raw = getTableData_NVMe(buf_smart, 112, 127, true);
            smartLog.power_on_hours.raw = getTableData_NVMe(buf_smart, 128, 143, true);
            smartLog.unsafe_shutdowns.raw = getTableData_NVMe(buf_smart, 144, 159, true);
            smartLog.media_errors.raw = getTableData_NVMe(buf_smart, 160, 175, true);
            smartLog.num_err_log_entries.raw = getTableData_NVMe(buf_smart, 176, 191, true);
            smartLog.warning_temp_time.raw = getTableData_NVMe(buf_smart, 192, 195, true);
            smartLog.critical_comp_time.raw = getTableData_NVMe(buf_smart, 196, 199, true);
        }

        private string getTableData_NVMe(byte[] smartTableArray, int begin, int finish, bool getRaw)
        {
            ulong tmp = 0;
            for (int i = finish; i >= begin; i--)
            {
                tmp = ((tmp << 8) + smartTableArray[i]);
            }
            if (getRaw)
                return tmp.ToString("X8");
            else
                return tmp.ToString();
        }

        #endregion

        /// <summary>
        /// 获取SMART信息
        /// </summary>
        public void getDeviceAndSmartData(string fullPath, ref byte[] deviceInfoArray, ref byte[] smartInfoArray)
        {
            string physicalPath = fullPath.Substring(4, fullPath.IndexOf(" ") - 4);

            IntPtr handle = CreateFile(
                            "\\\\.\\" + physicalPath,
                            EFileAccess.GenericAll,
                            EFileShare.Write | EFileShare.Read,
                            IntPtr.Zero,
                            ECreationDisposition.OpenExisting,
                            EFileAttributes.NoBuffering | EFileAttributes.RandomAccess,
                            IntPtr.Zero);

            NVMeClass NVMe = new NVMeClass();
            NVMe.getIDBuffer(handle, ref deviceInfoArray);
            NVMe.getSMARTBuffer(handle, ref smartInfoArray);

            CloseHandle(handle);
        }

        /// <summary>
        /// 解析SMART信息
        /// </summary>
        public void parseSmartInformation_NVMe(byte[] SMARTinfo, ref List<string[]> rowCollection)
        {
            SMARTLog_NVMe smartLog_nvme = new SMARTLog_NVMe();
            separatSmartValue_NVMe(SMARTinfo, ref smartLog_nvme);
            organizeSmartValue_NVMe(smartLog_nvme, ref rowCollection);
        }

    }
}
